package Tests;

import replicated_calculator.*;

import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;

public class TestMultipleLocalServersAndClients {

    private boolean callbackDone = false;
    private ArrayList<ServerReplicated> servers = new ArrayList<ServerReplicated>();
    private ArrayList<ClientNonRobust> clients = new ArrayList<ClientNonRobust>();
    int clientListenPort = 10001;
    int serverListenPort = 20001;
    int clientPortForServer = 30001;

    private static String localHostAddress;

    public static void main(String[] args) throws InterruptedException {
        try {
            localHostAddress = InetAddress.getLocalHost().getCanonicalHostName();
        }
        catch (UnknownHostException e) {
            System.out.println("Could not get local address");
            e.printStackTrace();
            System.exit(-1);
        }
        new TestMultipleLocalServersAndClients().testSpeedSwitching();
        Thread.currentThread().sleep(1000);
        new TestMultipleLocalServersAndClients().testHuge();
    }


    private void testSpeedSwitching() {
        report("\n***** TESTING SINGLE USER SPEED SWITCHING ******");
        final int nrOfMessages = 50;
        String loginName = "Gert";

        //create first server
        report("First server spawning");
        ServerReplicated firstServer = new ServerReplicated();
        firstServer.createGroup(Parameters.mainServerPortForServers);

        //join a second server
        report("Second server joining");
        ServerReplicated secondServer = new ServerReplicated();
        secondServer.setListenPort(serverListenPort, clientListenPort);
        secondServer.joinGroup(new InetSocketAddress(localHostAddress, Parameters.mainServerPortForServers));

        sleep(300);

        //add a client to the first server
        String firstServerAddress = localHostAddress + ":" + Parameters.mainServerPortForClients;
        String secondServerAddress = localHostAddress + ":" + clientListenPort;

        report("Client connecting to first server");
        ClientNonRobust switchingClient = new ClientNonRobust(clientPortForServer);
        if(!switchingClient.connect(firstServerAddress, loginName)){
            report("\tError connecting to first server"); System.exit(-1);
        }
        sleep(100);

        //Spam a lot of writes of x
        report("Write " + nrOfMessages + " events");
        for(int i = 1; i <= nrOfMessages; i++){
            switchingClient.assign("x", new BigInteger(i + ""));
        }

        //switch to the other server
        report("Disconnecting from first server");
        switchingClient.disconnect();
        switchingClient = new ClientNonRobust(clientPortForServer+1);

        report("Connect to second server");
        if(!switchingClient.connect(secondServerAddress, loginName)){
            report("\tError connecting to second server"); System.exit(-1);
        }

        final boolean[] resultDone = {false};

        report("Reading x at second server:");
        switchingClient.read("x", new Callback<BigInteger>() {
            @Override
            public void result(BigInteger res) {
                report("\tx=" + res + (res.intValue() == nrOfMessages ? " OK" : " FAIL"));
                resultDone[0] = true;
            }
        });

        while(!resultDone[0]){
            sleep(50);
        }

        switchingClient.disconnect();
        smallSleep();
        firstServer.leaveGroup();
        smallSleep();
        secondServer.leaveGroup();
    }

    private void testHuge() {
        report("\n****** TESTING 3 LOCAL SERVERS, EACH WITH 3 CLIENTS ******");

        //create first server
        ServerReplicated firstServer = new ServerReplicated();
        firstServer.createGroup(Parameters.mainServerPortForServers);
        servers.add(firstServer);
        report("First server: " + firstServer.toString());

        //add 3 clients to the server
        addClients(localHostAddress + ":" + Parameters.mainServerPortForClients, 3);

        //write x=42
        report("\nClient0 wrote x=42");
        clients.get(0).assign("x", new BigInteger("42"));

        //make all clients read the value of x
        readVarAllClients("x",42);



        //Add another server
        InetSocketAddress knownPeer = new InetSocketAddress(localHostAddress, Parameters.mainServerPortForServers);
        addServer(knownPeer);

        addClients(localHostAddress + ":" + (clientListenPort+servers.size()-1), 3);

        report("\nClient nr 4 wrote y=43");
        clients.get(4).assign("y", new BigInteger("43"));
        //make all clients read the value of y
        sleep(200);
        readVarAllClients("y",43);




        //Add third server
        knownPeer = new InetSocketAddress(localHostAddress, Parameters.mainServerPortForServers);
        addServer(knownPeer);

        addClients(localHostAddress + ":" + (clientListenPort+servers.size()-1), 3);

        report("\nClient nr 7 wrote z=44");
        clients.get(7).assign("z", new BigInteger("44"));
        //make all clients read the value of y
        sleep(200);
        readVarAllClients("z",44);

        report("\nAll clients should be able to access x since the servers are mirrored");
        readVarAllClients("x",42);



        //CLEANUP
        report("Cleaning up");
        for (ClientNonRobust client : clients) {
            client.disconnect();
            sleep(100);
        }
        for(ServerReplicated serverReplicated : servers){
            serverReplicated.leaveGroup();
            sleep(100);
        }
    }

    private void readVarAllClients(final String var, final int result){
        final boolean[] callbackDone = new boolean[clients.size()];

        System.out.print("READING " + var + ": ");
        for (int i = 0, clientsSize = clients.size(); i < clientsSize; i++) {
            final ClientNonRobust client = clients.get(i);
            System.out.print(client.getClientName() + " ");
            callbackDone[i] = false;
            final int j = i;
            client.read(var, new Callback<BigInteger>() {
                @Override
                public void result(BigInteger res) {
                    report("\t" + client.getClientName() + " reads " + var + "=" + res +
                    (res.intValue()==result ? " OK" : " FAIL"));
                    callbackDone[j] = true;
                }
            });
        }
        System.out.println();

        //check if all callbacks have returned
        boolean done = false;
        while(!done){
            done = true;
            for (boolean callbackEntry : callbackDone) {
                if (!callbackEntry) {
                    done = false;
                }
            }
            sleep(50);
        }

    }

    private void addServer(InetSocketAddress knownPeer){
        int serverNr = servers.size();
        ServerReplicated replicated = new ServerReplicated();
        replicated.setListenPort(serverListenPort+serverNr,clientListenPort+serverNr);
        replicated.joinGroup(knownPeer);
        servers.add(replicated);
        report("Added a server. Total=" + servers.size() + "\t\t" + replicated.toString());
        smallSleep();
    }

    private void addClients(String serverAddressAndPort, int nrOfClients){
        for(int i = 0; i<nrOfClients; i++){
            ClientNonRobust nonRobust = new ClientNonRobust((clientPortForServer+clients.size()));
            if ( !nonRobust.connect(serverAddressAndPort, "ClientNr" + clients.size())){
                report("ERROR CONNECTING CLIENT TO SERVER");
            }
            clients.add(nonRobust);
            report("Added a client. Total=" + clients.size() + "\t\t" + nonRobust.toString());
            smallSleep();
        }
    }




    private void smallSleep(){
        sleep(150);
    }

    private void sleep(int time){
        try {
            Thread.currentThread().sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized private void report(String str) {
        System.out.println(str);

    }
}